package ru.susu.algebra.partition;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import ru.susu.algebra.cache.SelfCleaningCache;
import ru.susu.algebra.pair.Pair;
import ru.susu.algebra.partition.filter.AllPartitionsFilter;

/**
 * @author akargapolov
 * @since: 01.03.2009
 */
public class PartitionsFacade
{
	private static SelfCleaningCache _cache = new SelfCleaningCache(10000);

	public static Partition getWithoutLastWithCache(Partition p)
	{
		String key = getWithoutLastKey(p);
		Partition res = (Partition) _cache.getValue(key);
		if (res == null)
		{
			Partition tmp = p.clone();
			tmp.remove(getLastNumber(tmp));
			res = getEqualsPartitionFromCache(tmp);
			_cache.put(key, res);
		}
		return res;
	}

	public static Partition getEqualsPartitionFromCache(Partition p)
	{
		PartitionsCacheConstructor constructor = new PartitionsCacheConstructor();
		List<Partition> partitions = constructor.getAscendingPartitions(p.getPresentedNumber(),
				new AllPartitionsFilter());
		// return partitions.get(Collections.binarySearch(partitions, p));
		return partitions.get(p.getOrderNumber());
	}

	public static Integer getLastNumber(Partition p)
	{
		Integer[] array = p.getArrayRepresentation();
		return array[array.length - 1];
	}

	public static List<Pair<Partition, Integer>> getPartitionsWithCache(Partition p1, Partition p2)
	{
		String key = getTwoPartitionsKey(p1, p2);
		List<Pair<Partition, Integer>> res = (List<Pair<Partition, Integer>>) _cache.getValue(key);
		if (res == null)
		{
			res = getPartitions(p1, p2);
			_cache.put(key, res);
		}
		return res;
	}

	private static List<Pair<Partition, Integer>> getPartitions(Partition p1, Partition p2)
	{
		ArrayList<Pair<Partition, Integer>> res = new ArrayList<Pair<Partition, Integer>>();
		int length = getLastNumber(p2);

		int arSize = p1.getLength();
		Integer[] array = p1.getArrayRepresentation();

		int i, j;

		for (i = 0; i < arSize; i++)
			for (j = 1; j <= array[i]; j++)
			{
				int len = getLength(array, arSize, i, j);
				if (len != length)
					continue;
				Integer[] b = removeCorner(array, arSize, i, j);
				Partition tmp = PartitionsFactory.createPartition(b);
				tmp = getEqualsPartitionFromCache(tmp);
				int footL = (length - (array[i] - j + 1)) % 2;
				res.add(new Pair<Partition, Integer>(tmp, footL));
			}

		return res;
	}

	private static int getLength(Integer[] array, Integer aSize, int i, int j)
	{
		int res = 0;
		res = array[i] - j;
		for (int k = 0; i - k >= 0 && array[i - k] >= j; k++)
			++res;
		return res;
	}

	public static Integer[] removeCorner(Integer[] a, Integer aSize, int i, int j)
	{
		Integer bSize = aSize;
		Integer[] b = Arrays.copyOf(a, aSize);
		int k;
		for (k = i; k > 0 && b[k - 1] > j; k--)
			b[k] = b[k - 1] - 1;
		while (k >= 0 && b[k] >= j)
			b[k--] = j - 1;
		int d = 0;
		while (d < aSize && b[d] == 0)
			d++;

		if (d > 0)
		{
			for (k = 0; k + d < aSize; k++)
				b[k] = b[k + d];
			bSize -= d;
		}

		return Arrays.copyOf(b, bSize);
	}

	private static String DELIMETER = "_";

	private static String getWithoutLastKey(Partition p1)
	{
		StringBuilder builder = new StringBuilder();
		builder.append("withoutLast");
		builder.append(DELIMETER);
		builder.append(p1.getPresentedNumber().toString());
		builder.append(DELIMETER);
		builder.append(p1.getOrderNumber().toString());
		return builder.toString();
	}

	private static String getTwoPartitionsKey(Partition p1, Partition p2)
	{
		StringBuilder builder = new StringBuilder();
		builder.append("twoPartitions");
		builder.append(DELIMETER);
		builder.append(p1.getPresentedNumber().toString());
		builder.append(DELIMETER);
		builder.append(p1.getOrderNumber().toString());
		builder.append(DELIMETER);
		builder.append(getLastNumber(p2));
		return builder.toString();
	}

	public static Partition getAssociatedPartition(Partition p)
	{
		Integer[] array = p.getArrayRepresentation();
		if (array.length == 0)
			return p;
		Integer[] assArray = new Integer[array[array.length - 1]];
		Arrays.fill(assArray, Integer.valueOf(0));
		for (int index = 0; index < array.length; index++)
		{
			for (int j = 0; j < array[index]; j++)
				assArray[assArray.length - 1 - j]++;
		}
		Partition res = PartitionsFactory.createPartition(assArray);
		return PartitionsFacade.getEqualsPartitionFromCache(res);
	}

	public static BigInteger getMainHooksProduct(Partition p)
	{
		BigInteger res = BigInteger.valueOf(1);
		for (Integer hook : getMainHookLengths(p))
		{
			res = res.multiply(BigInteger.valueOf(hook));
		}
		return res;
	}

	public static Integer[] getMainHookLengths(Partition p)
	{
		List<Integer> hooks = new ArrayList<Integer>();
		Integer[] array = p.getArrayRepresentation();
		int i, j, k;
		j = 1;
		for (i = array.length - 1; i >= 0; i--)
		{
			if (j > array[i])
				break;
			for (k = 0; i - k >= 0 && array[i - k] >= j; k++)
				;
			hooks.add(array[i] - j + k);
			j++;
		}
		Integer[] result = new Integer[hooks.size()];
		hooks.toArray(result);
		return result;
	}

	public static boolean isAlternatingSqrtCharacter(Partition p1, Partition p2)
	{
		Integer[] hooks = getMainHookLengths(p1);
		Arrays.sort(hooks);
		return Arrays.equals(hooks, p2.getArrayRepresentation());
	}

	public static BigInteger getElementsProduct(Partition p1)
	{
		BigInteger res = BigInteger.ONE;
		for (Integer item : p1.getArrayRepresentation())
		{
			res = res.multiply(BigInteger.valueOf(item));
		}
		return res;
	}
}
